/*
 * rk817 charger driver
 *
 * Copyright (C) 2018 Rockchip Electronics Co., Ltd
 * xsf <xsf@rock-chips.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 */

#include <linux/delay.h>
#include <linux/extcon.h>
#include <linux/gpio.h>
#include <linux/iio/consumer.h>
#include <linux/iio/iio.h>
#include <linux/irq.h>
#include <linux/jiffies.h>
#include <linux/mfd/rk808.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/power/rk_usbbc.h>
#include <linux/regmap.h>
#include <linux/rtc.h>
#include <linux/timer.h>
#include <linux/workqueue.h>

static int dbg_enable;
module_param_named(dbg_level, dbg_enable, int, 0644);

#define DBG(args...) \
    do { \
        if (dbg_enable) { \
            pr_info(args); \
        } \
    } while (0)

#define CHARGE_DRIVER_VERSION        "1.0"

#define DISABLE                 0x00
#define ENABLE                  0x01
#define OTG_SLP_ENABLE          0x01
#define OTG_SLP_DISABLE         0x00
#define OTG_ENABLE              0x11
#define OTG_DISABLE             0x10
#define RK817_BOOST_ENABLE      0x11
#define RK817_BOOST_DISABLE     0x10
#define OTG_MODE                0x01
#define OTG_MODE_ON             0x01
#define DEFAULT_INPUT_VOLTAGE   4500
#define DEFAULT_INPUT_CURRENT   2000
#define DEFAULT_CHRG_VOLTAGE    4200
#define DEFAULT_CHRG_CURRENT    1400
#define DEFAULT_CHRG_TERM_MODE  1
#define DEFAULT_CHRG_TERM_CUR   150
#define SAMPLE_RES_10MR         10
#define SAMPLE_RES_20MR         20
#define SAMPLE_RES_DIV1         1
#define SAMPLE_RES_DIV2         2

#define INPUT_450MA             450
#define INPUT_1500MA            1500

#define CURRENT_TO_ADC(current, samp_res)    \
    (current * 1000 * samp_res / 172)

enum charge_current {
    CHRG_CUR_1000MA,
    CHRG_CUR_1500MA,
    CHRG_CUR_2000MA,
    CHRG_CUR_2500MA,
    CHRG_CUR_2750MA,
    CHRG_CUR_3000MA,
    CHRG_CUR_3500MA,
    CHRG_CUR_500MA,
};

enum charge_voltage {
    CHRG_VOL_4100MV,
    CHRG_VOL_4150MV,
    CHRG_VOL_4200MV,
    CHRG_VOL_4250MV,
    CHRG_VOL_4300MV,
    CHRG_VOL_4350MV,
    CHRG_VOL_4400MV,
    CHRG_VOL_4450MV,
};

enum input_voltage {
    INPUT_VOL_4000MV,
    INPUT_VOL_4100MV,
    INPUT_VOL_4200MV,
    INPUT_VOL_4300MV,
    INPUT_VOL_4400MV,
    INPUT_VOL_4500MV,
    INPUT_VOL_4600MV,
    INPUT_VOL_4700MV,
};

enum input_current {
    INPUT_CUR_450MA,
    INPUT_CUR_80MA,
    INPUT_CUR_850MA,
    INPUT_CUR_1500MA,
    INPUT_CUR_1750MA,
    INPUT_CUR_2000MA,
    INPUT_CUR_2500MA,
    INPUT_CUR_3000MA,
};

enum charge_clk {
    CHRG_CLK_1M,
    CHRG_CLK_2M,
};

enum charge_term_sel {
    CHRG_TERM_150MA,
    CHRG_TERM_200MA,
    CHRG_TERM_300MA,
    CHRG_TERM_400MA,
};

enum charge_timer_trickle {
    CHRG_TIMER_TRIKL_30MIN,
    CHRG_TIMER_TRIKL_45MIN,
    CHRG_TIMER_TRIKL_60MIN,
    CHRG_TIMER_TRIKL_90MIN,
    CHRG_TIMER_TRIKL_120MIN,
    CHRG_TIMER_TRIKL_150MIN,
    CHRG_TIMER_TRIKL_180MIN,
    CHRG_TIMER_TRIKL_210MIN,
};

enum charge_timer_cccv {
    CHRG_TIMER_CCCV_4H,
    CHRG_TIMER_CCCV_5H,
    CHRG_TIMER_CCCV_6H,
    CHRG_TIMER_CCCV_8H,
    CHRG_TIMER_CCCV_10H,
    CHRG_TIMER_CCCV_12H,
    CHRG_TIMER_CCCV_14H,
    CHRG_TIMER_CCCV_16H,
};

enum charge_status {
    CHRG_OFF,
    DEAD_CHRG,
    TRICKLE_CHRG,
    CC_OR_CV_CHRG,
    CHRG_TERM,
    USB_OVER_VOL,
    BAT_TMP_ERR,
    BAT_TIM_ERR,
};

enum discharge_ilimit {
    DISCHRG_2000MA,
    DISCHRG_2500MA,
    DISCHRG_3000MA,
    DISCHRG_3500MA,
    DISCHRG_4000MA,
};

enum bat_system_comp_time {
    DLY_20US,
    DLY_10US,
    DLY_40US,
    DLY_20US_AGAIN,
};

enum charge_term_mode {
    CHRG_ANALOG,
    CHRG_DIGITAL,
};

enum charger_t {
    USB_TYPE_UNKNOWN_CHARGER,
    USB_TYPE_NONE_CHARGER,
    USB_TYPE_USB_CHARGER,
    USB_TYPE_AC_CHARGER,
    USB_TYPE_CDP_CHARGER,
    DC_TYPE_DC_CHARGER,
    DC_TYPE_NONE_CHARGER,
};

enum charger_state {
    OFFLINE = 0,
    ONLINE
};

enum rk817_charge_fields {
    BOOST_EN, OTG_EN, OTG_SLP_EN, CHRG_CLK_SEL,
    CHRG_EN, CHRG_VOL_SEL, CHRG_CT_EN, CHRG_CUR_SEL,
    USB_VLIM_EN, USB_VLIM_SEL, USB_ILIM_EN, USB_ILIM_SEL,
    SYS_CAN_SD,  USB_SYS_EN, BAT_OVP_EN, CHRG_TERM_ANA_DIG,
    CHRG_TERM_ANA_SEL,
    CHRG_TERM_DIG,
    BAT_HTS_TS, BAT_LTS_TS,
    CHRG_TIMER_TRIKL_EN, CHRG_TIMER_TRIKL,
    CHRG_TIMER_CCCV_EN, CHRG_TIMER_CCCV,
    BAT_EXS, CHG_STS, BAT_OVP_STS, CHRG_IN_CLAMP,
    USB_EXS, USB_EFF,
    BAT_DIS_ILIM_STS, BAT_SYS_CMP_DLY, BAT_DIS_ILIM_EN,
    BAT_DISCHRG_ILIM,
    PLUG_IN_STS, SOC_REG0, SOC_REG1, SOC_REG2,
    F_MAX_FIELDS
};

static const struct reg_field rk817_charge_reg_fields[] = {
    [SOC_REG0] = REG_FIELD(0x9A, 0, 7),
    [SOC_REG1] = REG_FIELD(0x9B, 0, 7),
    [SOC_REG2] = REG_FIELD(0x9C, 0, 7),
    [BOOST_EN] = REG_FIELD(0xB4, 1, 5),
    [OTG_EN] = REG_FIELD(0xB4, 2, 6),
    [OTG_SLP_EN] = REG_FIELD(0xB5, 6, 6),
    [CHRG_EN] = REG_FIELD(0xE4, 7, 7),
    [CHRG_VOL_SEL] = REG_FIELD(0xE4, 4, 6),
    [CHRG_CT_EN] = REG_FIELD(0xE4, 3, 3),
    [CHRG_CUR_SEL] = REG_FIELD(0xE4, 0, 2),

    [USB_VLIM_EN] = REG_FIELD(0xE5, 7, 7),
    [USB_VLIM_SEL] = REG_FIELD(0xE5, 4, 6),
    [USB_ILIM_EN] = REG_FIELD(0xE5, 3, 3),
    [USB_ILIM_SEL] = REG_FIELD(0xE5, 0, 2),

    [SYS_CAN_SD] = REG_FIELD(0xE6, 7, 7),
    [USB_SYS_EN] = REG_FIELD(0xE6, 6, 6),
    [BAT_OVP_EN] = REG_FIELD(0xE6, 3, 3),
    [CHRG_TERM_ANA_DIG] = REG_FIELD(0xE6, 2, 2),
    [CHRG_TERM_ANA_SEL] = REG_FIELD(0xE6, 0, 1),

    [CHRG_TERM_DIG] = REG_FIELD(0xE7, 0, 7),

    [BAT_HTS_TS] = REG_FIELD(0xE8, 0, 7),

    [BAT_LTS_TS] = REG_FIELD(0xE9, 0, 7),

    [CHRG_TIMER_TRIKL_EN] = REG_FIELD(0xEA, 7, 7),
    [CHRG_TIMER_TRIKL] = REG_FIELD(0xEA, 4, 6),
    [CHRG_TIMER_CCCV_EN] = REG_FIELD(0xEA, 3, 3),
    [CHRG_TIMER_CCCV] = REG_FIELD(0xEA, 0, 2),

    [BAT_EXS] = REG_FIELD(0xEB, 7, 7),
    [CHG_STS] = REG_FIELD(0xEB, 4, 6),
    [BAT_OVP_STS] = REG_FIELD(0xEB, 3, 3),
    [CHRG_IN_CLAMP] = REG_FIELD(0xEB, 2, 2),
    [USB_EXS] = REG_FIELD(0xEB, 1, 1),
    [USB_EFF] = REG_FIELD(0xEB, 0, 0),

    [BAT_DIS_ILIM_STS] = REG_FIELD(0xEC, 6, 6),
    [BAT_SYS_CMP_DLY] = REG_FIELD(0xEC, 4, 5),
    [BAT_DIS_ILIM_EN] = REG_FIELD(0xEC, 3, 3),
    [BAT_DISCHRG_ILIM] = REG_FIELD(0xEC, 0, 2),
    [PLUG_IN_STS] = REG_FIELD(0xf0, 6, 6),
    [CHRG_CLK_SEL] = REG_FIELD(0xF3, 6, 6),
};

struct charger_platform_data {
    u32 max_input_current;
    u32 min_input_voltage;

    u32 max_chrg_current;
    u32 max_chrg_voltage;

    u32 chrg_finish_cur;
    u32 chrg_term_mode;

    u32 power_dc2otg;
    u32 dc_det_level;
    int dc_det_pin;
    bool support_dc_det;
    int virtual_power;
    int sample_res;
    int otg5v_suspend_enable;
    bool extcon;
    int gate_function_disable;
};

struct rk817_charger {
    struct i2c_client *client;
    struct platform_device *pdev;
    struct device *dev;
    struct rk808 *rk817;
    struct regmap *regmap;
    struct regmap_field *rmap_fields[F_MAX_FIELDS];

    struct power_supply *ac_psy;
    struct power_supply *usb_psy;
    struct extcon_dev *cable_edev;
    struct charger_platform_data *pdata;
    struct workqueue_struct *usb_charger_wq;
    struct workqueue_struct *dc_charger_wq;
    struct delayed_work dc_work;
    struct delayed_work usb_work;
    struct delayed_work host_work;
    struct delayed_work discnt_work;
    struct delayed_work irq_work;
    struct notifier_block bc_nb;
    struct notifier_block cable_cg_nb;
    struct notifier_block cable_host_nb;
    struct notifier_block cable_discnt_nb;
    unsigned int bc_event;
    enum charger_t usb_charger;
    enum charger_t dc_charger;
    struct regulator *otg5v_rdev;
    u8 ac_in;
    u8 usb_in;
    u8 otg_in;
    u8 dc_in;
    u8 prop_status;

    u32 max_input_current;
    u32 min_input_voltage;

    u32 max_chrg_current;
    u32 max_chrg_voltage;

    u32 chrg_finish_cur;
    u32 chrg_term_mode;

    u8 res_div;
    u8 otg_slp_state;
    u8 plugin_trigger;
    u8 plugout_trigger;
    int plugin_irq;
    int plugout_irq;
};

static enum power_supply_property rk817_ac_props[] = {
    POWER_SUPPLY_PROP_ONLINE,
    POWER_SUPPLY_PROP_STATUS,
    POWER_SUPPLY_PROP_VOLTAGE_MAX,
    POWER_SUPPLY_PROP_CURRENT_MAX,
};

static enum power_supply_property rk817_usb_props[] = {
    POWER_SUPPLY_PROP_ONLINE,
    POWER_SUPPLY_PROP_STATUS,
    POWER_SUPPLY_PROP_VOLTAGE_MAX,
    POWER_SUPPLY_PROP_CURRENT_MAX,
};

static int rk817_charge_ac_get_property(struct power_supply *psy,
                    enum power_supply_property psp,
                    union power_supply_propval *val)
{
    struct rk817_charger *charge = power_supply_get_drvdata(psy);
    int ret = 0;

    switch (psp) {
    case POWER_SUPPLY_PROP_ONLINE:
        if (charge->pdata->virtual_power)
            val->intval = 1;
        else
            val->intval = (charge->ac_in | charge->dc_in);

        DBG("ac report online: %d\n", val->intval);
        break;
    case POWER_SUPPLY_PROP_STATUS:
        if (charge->pdata->virtual_power)
            val->intval = POWER_SUPPLY_STATUS_CHARGING;
        else
            val->intval = charge->prop_status;

        DBG("report prop: %d\n", val->intval);
        break;
    case POWER_SUPPLY_PROP_VOLTAGE_MAX:
        val->intval = charge->max_chrg_voltage * 1000;    /* uV */
        break;
    case POWER_SUPPLY_PROP_CURRENT_MAX:
        val->intval = charge->max_chrg_current * 1000;    /* uA */
        break;
    default:
        ret = -EINVAL;
        break;
    }

    return ret;
}

static int rk817_charge_usb_get_property(struct power_supply *psy,
                     enum power_supply_property psp,
                     union power_supply_propval *val)
{
    struct rk817_charger *charge = power_supply_get_drvdata(psy);
    int ret = 0;

    switch (psp) {
    case POWER_SUPPLY_PROP_ONLINE:
        if (charge->pdata->virtual_power)
            val->intval = 1;
        else
            val->intval = charge->usb_in;

        DBG("usb report online: %d\n", val->intval);
        break;
    case POWER_SUPPLY_PROP_STATUS:
        if (charge->pdata->virtual_power)
            val->intval = POWER_SUPPLY_STATUS_CHARGING;
        else
            val->intval = charge->prop_status;

        DBG("report prop: %d\n", val->intval);
        break;
    case POWER_SUPPLY_PROP_VOLTAGE_MAX:
        val->intval = charge->max_chrg_voltage;
        break;
    case POWER_SUPPLY_PROP_CURRENT_MAX:
        val->intval = charge->max_chrg_current;
        break;
    default:
        ret = -EINVAL;
        break;
    }

    return ret;
}

static const struct power_supply_desc rk817_ac_desc = {
    .name        = "ac",
    .type        = POWER_SUPPLY_TYPE_MAINS,
    .properties    = rk817_ac_props,
    .num_properties    = ARRAY_SIZE(rk817_ac_props),
    .get_property    = rk817_charge_ac_get_property,
};

static const struct power_supply_desc rk817_usb_desc = {
    .name        = "usb",
    .type        = POWER_SUPPLY_TYPE_USB,
    .properties    = rk817_usb_props,
    .num_properties    = ARRAY_SIZE(rk817_usb_props),
    .get_property    = rk817_charge_usb_get_property,
};

static int rk817_charge_init_power_supply(struct rk817_charger *charge)
{
    struct power_supply_config psy_cfg = { .drv_data = charge, };

    charge->usb_psy = devm_power_supply_register(charge->dev,
                             &rk817_usb_desc,
                             &psy_cfg);
    if (IS_ERR(charge->usb_psy)) {
        dev_err(charge->dev, "register usb power supply fail\n");
        return PTR_ERR(charge->usb_psy);
    }

    charge->ac_psy = devm_power_supply_register(charge->dev, &rk817_ac_desc,
                        &psy_cfg);
    if (IS_ERR(charge->ac_psy)) {
        dev_err(charge->dev, "register ac power supply fail\n");
        return PTR_ERR(charge->ac_psy);
    }

    return 0;
}

static int rk817_charge_field_read(struct rk817_charger *charge,
                   enum rk817_charge_fields field_id)
{
    int ret;
    int val;

    ret = regmap_field_read(charge->rmap_fields[field_id], &val);
    if (ret < 0)
        return ret;

    return val;
}

static int rk817_charge_field_write(struct rk817_charger *charge,
                    enum rk817_charge_fields field_id,
                    unsigned int val)
{
    return regmap_field_write(charge->rmap_fields[field_id], val);
}

static int rk817_charge_get_otg_state(struct rk817_charger *charge)
{
    return regulator_is_enabled(charge->otg5v_rdev);
}

static void rk817_charge_boost_disable(struct rk817_charger *charge)
{
    rk817_charge_field_write(charge, BOOST_EN, RK817_BOOST_DISABLE);
}

static void rk817_charge_boost_enable(struct rk817_charger *charge)
{
    rk817_charge_field_write(charge, BOOST_EN, RK817_BOOST_ENABLE);
}

static void rk817_charge_otg_disable(struct rk817_charger *charge)
{
    int ret;

    ret = regulator_disable(charge->otg5v_rdev);

    if (ret) {
        DBG("disable otg5v failed:%d\n", ret);
        return;
    }

    return;
}

static void rk817_charge_otg_enable(struct rk817_charger *charge)
{
    int ret;

    ret = regulator_enable(charge->otg5v_rdev);

    if (ret) {
        DBG("enable otg5v failed:%d\n", ret);
        return;
    }

    return;
}

#ifdef CONFIG_PM_SLEEP
static int rk817_charge_get_otg_slp_state(struct rk817_charger *charge)
{
    return (rk817_charge_field_read(charge, OTG_SLP_EN) & OTG_SLP_ENABLE);
}

static void rk817_charge_otg_slp_disable(struct rk817_charger *charge)
{
    rk817_charge_field_write(charge, OTG_SLP_EN, OTG_SLP_DISABLE);
}

static void rk817_charge_otg_slp_enable(struct rk817_charger *charge)
{
    rk817_charge_field_write(charge, OTG_SLP_EN, OTG_SLP_ENABLE);
}
#endif

static int rk817_charge_get_charge_state(struct rk817_charger *charge)
{
    return rk817_charge_field_read(charge, CHRG_EN);
}

static void rk817_charge_enable_charge(struct rk817_charger *charge)
{
    rk817_charge_field_write(charge, CHRG_EN, ENABLE);
}

static void rk817_charge_usb_to_sys_enable(struct rk817_charger *charge)
{
    rk817_charge_field_write(charge, USB_SYS_EN, ENABLE);
}

static void rk817_charge_sys_can_sd_disable(struct rk817_charger *charge)
{
    rk817_charge_field_write(charge, SYS_CAN_SD, DISABLE);
}

static int rk817_charge_get_charge_status(struct rk817_charger *charge)
{
    int status;

    status = rk817_charge_field_read(charge, CHG_STS);

    switch (status) {
    case CHRG_OFF:
        DBG("charge off...\n");
        break;
    case DEAD_CHRG:
        DBG("dead charge...\n");
        break;
    case TRICKLE_CHRG:
        DBG("trickle charge...\n");
        break;
    case CC_OR_CV_CHRG:
        DBG("CC or CV charge...\n");
        break;
    case CHRG_TERM:
        DBG("charge TERM...\n");
        break;
    case USB_OVER_VOL:
        DBG("USB over voltage...\n");
        break;
    case BAT_TMP_ERR:
        DBG("battery temperature error...\n");
        break;
    case BAT_TIM_ERR:
        DBG("battery timer error..\n");
        break;
    default:
        break;
    }

    return status;
}

static int rk817_charge_get_plug_in_status(struct rk817_charger *charge)
{
    return rk817_charge_field_read(charge, PLUG_IN_STS);
}

static void rk817_charge_set_charge_clock(struct rk817_charger *charge,
                      enum charge_clk clock)
{
    rk817_charge_field_write(charge, CHRG_CLK_SEL, clock);
}

static int is_battery_exist(struct rk817_charger *charge)
{
    return rk817_charge_field_read(charge, BAT_EXS);
}

static void rk817_charge_set_chrg_voltage(struct rk817_charger *charge,
                      int chrg_vol)
{
    int voltage;

    if (chrg_vol < 4100 || chrg_vol > 4500) {
        dev_err(charge->dev, "the charge voltage is error!\n");
    } else {
        voltage = (chrg_vol - 4100) / 50;
        rk817_charge_field_write(charge,
                     CHRG_VOL_SEL,
                     CHRG_VOL_4100MV + voltage);
    }
}

static void rk817_charge_set_chrg_current(struct rk817_charger *charge,
                      int chrg_current)
{
    if (chrg_current < 500 || chrg_current > 3500)
        dev_err(charge->dev, "the charge current is error!\n");

    if (chrg_current < 1000)
        rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_500MA);
    else if (chrg_current < 1500)
        rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_1000MA);
    else if (chrg_current < 2000)
        rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_1500MA);
    else if (chrg_current < 2500)
        rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_2000MA);
    else if (chrg_current < 3000)
        rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_2500MA);
    else if (chrg_current < 3500)
        rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_3000MA);
    else
        rk817_charge_field_write(charge, CHRG_CUR_SEL, CHRG_CUR_3500MA);
}

static void rk817_charge_vlimit_enable(struct rk817_charger *charge)
{
    rk817_charge_field_write(charge, USB_VLIM_EN, ENABLE);
}

static void rk817_charge_set_input_voltage(struct rk817_charger *charge,
                       int input_voltage)
{
    int voltage;

    if (input_voltage < 4000)
        dev_err(charge->dev, "the input voltage is error.\n");

    voltage = INPUT_VOL_4000MV + (input_voltage - 4000) / 100;

    rk817_charge_field_write(charge, USB_VLIM_SEL, voltage);
    rk817_charge_vlimit_enable(charge);
}

static void rk817_charge_ilimit_enable(struct rk817_charger *charge)
{
    rk817_charge_field_write(charge, USB_ILIM_EN, ENABLE);
}

static void rk817_charge_set_input_current(struct rk817_charger *charge,
                       int input_current)
{
    if (input_current < 80 || input_current > 3000)
        dev_err(charge->dev, "the input current is error.\n");

    if (input_current < 450)
        rk817_charge_field_write(charge, USB_ILIM_SEL,
                     INPUT_CUR_80MA);
    else if (input_current < 850)
        rk817_charge_field_write(charge, USB_ILIM_SEL,
                     INPUT_CUR_450MA);
    else if (input_current < 1500)
        rk817_charge_field_write(charge, USB_ILIM_SEL,
                     INPUT_CUR_850MA);
    else if (input_current < 1750)
        rk817_charge_field_write(charge, USB_ILIM_SEL,
                     INPUT_CUR_1500MA);
    else if (input_current < 2000)
        rk817_charge_field_write(charge, USB_ILIM_SEL,
                     INPUT_CUR_1750MA);
    else if (input_current < 2500)
        rk817_charge_field_write(charge, USB_ILIM_SEL,
                     INPUT_CUR_2000MA);
    else if (input_current < 3000)
        rk817_charge_field_write(charge, USB_ILIM_SEL,
                     INPUT_CUR_2500MA);
    else
        rk817_charge_field_write(charge, USB_ILIM_SEL,
                     INPUT_CUR_3000MA);

    rk817_charge_ilimit_enable(charge);
}

static void rk817_charge_set_chrg_term_mod(struct rk817_charger *charge,
                       int mode)
{
    rk817_charge_field_write(charge, CHRG_TERM_ANA_DIG, mode);
}

static void rk817_charge_set_term_current_analog(struct rk817_charger *charge,
                         int chrg_current)
{
    int value;

    if (chrg_current < 200)
        value = CHRG_TERM_150MA;
    else if (chrg_current < 300)
        value = CHRG_TERM_200MA;
    else if (chrg_current < 400)
        value = CHRG_TERM_300MA;
    else
        value = CHRG_TERM_400MA;

    rk817_charge_field_write(charge,
                 CHRG_TERM_ANA_SEL,
                 value);
}

static void rk817_charge_set_term_current_digital(struct rk817_charger *charge,
                          int chrg_current)
{
    int value;
    u8 current_adc;

    value = CURRENT_TO_ADC(chrg_current, charge->res_div);

    value &= (0xff << 5);
    current_adc = value >> 5;
    rk817_charge_field_write(charge, CHRG_TERM_DIG, current_adc);
}

static void rk817_charge_set_chrg_finish_condition(struct rk817_charger *charge)
{
    if (charge->chrg_term_mode == CHRG_ANALOG)
        rk817_charge_set_term_current_analog(charge,
                             charge->chrg_finish_cur);
    else
        rk817_charge_set_term_current_digital(charge,
                              charge->chrg_finish_cur);

    rk817_charge_set_chrg_term_mod(charge, charge->chrg_term_mode);
}

static int rk817_charge_online(struct rk817_charger *charge)
{
    return (charge->ac_in | charge->usb_in | charge->dc_in);
}

static int rk817_charge_get_dsoc(struct rk817_charger *charge)
{
    int soc_save;

    soc_save = rk817_charge_field_read(charge, SOC_REG0);
    soc_save |= (rk817_charge_field_read(charge, SOC_REG1) << 8);
    soc_save |= (rk817_charge_field_read(charge, SOC_REG2) << 16);

    return soc_save / 1000;
}

static void rk817_charge_set_otg_in(struct rk817_charger *charge, int online)
{
    charge->otg_in = online;
}

static void rk817_charge_set_chrg_param(struct rk817_charger *charge,
                    enum charger_t charger)
{
    switch (charger) {
    case USB_TYPE_NONE_CHARGER:
        charge->usb_in = 0;
        charge->ac_in = 0;
        if (charge->dc_in == 0) {
            charge->prop_status = POWER_SUPPLY_STATUS_DISCHARGING;
            rk817_charge_set_input_current(charge, INPUT_450MA);
        }
        power_supply_changed(charge->usb_psy);
        power_supply_changed(charge->ac_psy);
        break;
    case USB_TYPE_USB_CHARGER:
        charge->usb_in = 1;
        charge->ac_in = 0;
        charge->prop_status = POWER_SUPPLY_STATUS_CHARGING;
        if (charge->dc_in == 0)
            rk817_charge_set_input_current(charge, INPUT_450MA);
        power_supply_changed(charge->usb_psy);
        power_supply_changed(charge->ac_psy);
        break;
    case USB_TYPE_AC_CHARGER:
    case USB_TYPE_CDP_CHARGER:
        charge->ac_in = 1;
        charge->usb_in = 0;
        charge->prop_status = POWER_SUPPLY_STATUS_CHARGING;
        if (charger == USB_TYPE_AC_CHARGER)
            rk817_charge_set_input_current(charge,
                               charge->max_input_current);
        else
            rk817_charge_set_input_current(charge,
                               INPUT_1500MA);
        power_supply_changed(charge->usb_psy);
        power_supply_changed(charge->ac_psy);
        break;
    case DC_TYPE_DC_CHARGER:
        charge->dc_in = 1;
        charge->prop_status = POWER_SUPPLY_STATUS_CHARGING;
        rk817_charge_set_input_current(charge,
                           charge->max_input_current);
        power_supply_changed(charge->usb_psy);
        power_supply_changed(charge->ac_psy);
        break;
    case DC_TYPE_NONE_CHARGER:
        charge->dc_in = 0;
        if (!rk817_charge_get_plug_in_status(charge)) {
            charge->ac_in = 0;
            charge->usb_in = 0;
            charge->prop_status = POWER_SUPPLY_STATUS_DISCHARGING;
            rk817_charge_set_input_current(charge, INPUT_450MA);
        } else if (charge->usb_in) {
            rk817_charge_set_input_current(charge, INPUT_450MA);
            charge->prop_status = POWER_SUPPLY_STATUS_CHARGING;
        }
        power_supply_changed(charge->usb_psy);
        power_supply_changed(charge->ac_psy);
        break;
    default:
        charge->prop_status = POWER_SUPPLY_STATUS_DISCHARGING;
        rk817_charge_set_input_current(charge, INPUT_450MA);
        break;
    }

    if (rk817_charge_online(charge) && rk817_charge_get_dsoc(charge) == 100)
        charge->prop_status = POWER_SUPPLY_STATUS_FULL;
}

static void rk817_charge_set_otg_state(struct rk817_charger *charge, int state)
{
    switch (state) {
    case USB_OTG_POWER_ON:
        if (charge->otg_in) {
            DBG("otg5v is on yet, ignore..\n");
        } else {

            if (!rk817_charge_get_otg_state(charge)) {
                rk817_charge_otg_enable(charge);
                if (!rk817_charge_get_otg_state(charge)) {
                    DBG("enable otg5v failed\n");
                    return;
                }
            }
            disable_irq(charge->plugin_irq);
            disable_irq(charge->plugout_irq);
            DBG("enable otg5v\n");
        }
        break;

    case USB_OTG_POWER_OFF:
        if (!charge->otg_in) {
            DBG("otg5v is off yet, ignore..\n");
        } else {

            if (rk817_charge_get_otg_state(charge)) {
                rk817_charge_otg_disable(charge);
                if (rk817_charge_get_otg_state(charge)) {
                    DBG("disable otg5v failed\n");
                    return;
                }
            }
            enable_irq(charge->plugin_irq);
            enable_irq(charge->plugout_irq);
            DBG("disable otg5v\n");
        }
        break;
    default:
        dev_err(charge->dev, "error otg type\n");
        break;
    }
}

static irqreturn_t rk817_charge_dc_det_isr(int irq, void *charger)
{
    struct rk817_charger *charge = (struct rk817_charger *)charger;

    if (gpio_get_value(charge->pdata->dc_det_pin))
        irq_set_irq_type(irq, IRQF_TRIGGER_LOW);
    else
        irq_set_irq_type(irq, IRQF_TRIGGER_HIGH);

    queue_delayed_work(charge->dc_charger_wq, &charge->dc_work,
               msecs_to_jiffies(10));

    return IRQ_HANDLED;
}

static enum charger_t rk817_charge_get_dc_state(struct rk817_charger *charge)
{
    int level;

    if (!gpio_is_valid(charge->pdata->dc_det_pin))
        return DC_TYPE_NONE_CHARGER;

    level = gpio_get_value(charge->pdata->dc_det_pin);

    return (level == charge->pdata->dc_det_level) ?
        DC_TYPE_DC_CHARGER : DC_TYPE_NONE_CHARGER;
}

static void rk817_charge_dc_det_worker(struct work_struct *work)
{
    enum charger_t charger;
    struct rk817_charger *charge = container_of(work,
            struct rk817_charger, dc_work.work);

    charger = rk817_charge_get_dc_state(charge);
    if (charger == DC_TYPE_DC_CHARGER) {
        DBG("detect dc charger in..\n");
        rk817_charge_set_chrg_param(charge, DC_TYPE_DC_CHARGER);
        /* check otg supply */
        if (charge->otg_in && charge->pdata->power_dc2otg) {
            DBG("otg power from dc adapter\n");
            rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF);
        }

        rk817_charge_boost_disable(charge);
    } else {
        DBG("detect dc charger out..\n");
        rk817_charge_set_chrg_param(charge, DC_TYPE_NONE_CHARGER);
        rk817_charge_boost_enable(charge);
        /* check otg supply, power on anyway */
        if (charge->otg_in)
            rk817_charge_set_otg_state(charge, USB_OTG_POWER_ON);
    }
}

static int rk817_charge_init_dc(struct rk817_charger *charge)
{
    int ret, level;
    unsigned long irq_flags;
    unsigned int dc_det_irq;

    charge->dc_charger_wq = alloc_ordered_workqueue("%s",
                WQ_MEM_RECLAIM | WQ_FREEZABLE,
                "rk817-dc-wq");
    INIT_DELAYED_WORK(&charge->dc_work, rk817_charge_dc_det_worker);
    charge->dc_charger = DC_TYPE_NONE_CHARGER;

    if (!charge->pdata->support_dc_det)
        return 0;

    ret = devm_gpio_request(charge->dev,
                charge->pdata->dc_det_pin,
                "rk817_dc_det");
    if (ret < 0) {
        dev_err(charge->dev, "failed to request gpio %d\n",
            charge->pdata->dc_det_pin);
        return ret;
    }

    ret = gpio_direction_input(charge->pdata->dc_det_pin);
    if (ret) {
        dev_err(charge->dev, "failed to set gpio input\n");
        return ret;
    }

    level = gpio_get_value(charge->pdata->dc_det_pin);
    if (level == charge->pdata->dc_det_level)
        charge->dc_charger = DC_TYPE_DC_CHARGER;
    else
        charge->dc_charger = DC_TYPE_NONE_CHARGER;

    if (level)
        irq_flags = IRQF_TRIGGER_LOW;
    else
        irq_flags = IRQF_TRIGGER_HIGH;

    dc_det_irq = gpio_to_irq(charge->pdata->dc_det_pin);
    ret = devm_request_irq(charge->dev, dc_det_irq, rk817_charge_dc_det_isr,
                   irq_flags, "rk817_dc_det", charge);
    if (ret != 0) {
        dev_err(charge->dev, "rk817_dc_det_irq request failed!\n");
        return ret;
    }

    enable_irq_wake(dc_det_irq);

    if (charge->dc_charger != DC_TYPE_NONE_CHARGER)
        rk817_charge_set_chrg_param(charge, charge->dc_charger);

    return 0;
}

static void rk817_charge_host_evt_worker(struct work_struct *work)
{
    struct rk817_charger *charge = container_of(work,
            struct rk817_charger, host_work.work);
    struct extcon_dev *edev = charge->cable_edev;

    /* Determine cable/charger type */
    if (extcon_get_state(edev, EXTCON_USB_VBUS_EN) > 0) {
        DBG("receive type-c notifier event: OTG ON...\n");
        if (charge->dc_in && charge->pdata->power_dc2otg) {
            if (charge->otg_in)
                rk817_charge_set_otg_state(charge,
                               USB_OTG_POWER_OFF);
            DBG("otg power from dc adapter\n");
        } else {
            rk817_charge_set_otg_state(charge, USB_OTG_POWER_ON);
        }
        rk817_charge_set_otg_in(charge, ONLINE);
    } else if (extcon_get_state(edev, EXTCON_USB_VBUS_EN) == 0) {
        DBG("receive type-c notifier event: OTG OFF...\n");
        rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF);
        rk817_charge_set_otg_in(charge, OFFLINE);
    }
}

static void rk817_charger_evt_worker(struct work_struct *work)
{
    struct rk817_charger *charge = container_of(work,
                struct rk817_charger, usb_work.work);
    struct extcon_dev *edev = charge->cable_edev;
    enum charger_t charger = USB_TYPE_UNKNOWN_CHARGER;
    static const char * const event[] = {"UN", "NONE", "USB",
                         "AC", "CDP1.5A"};

    /* Determine cable/charger type */
    if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0)
        charger = USB_TYPE_USB_CHARGER;
    else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0)
        charger = USB_TYPE_AC_CHARGER;
    else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0)
        charger = USB_TYPE_CDP_CHARGER;

    if (charger != USB_TYPE_UNKNOWN_CHARGER) {
        DBG("receive type-c notifier event: %s...\n",
            event[charger]);
        charge->usb_charger = charger;
        rk817_charge_set_chrg_param(charge, charger);
    }
}

static void rk817_charge_discnt_evt_worker(struct work_struct *work)
{
    struct rk817_charger *charge = container_of(work,
            struct rk817_charger, discnt_work.work);

    if (extcon_get_state(charge->cable_edev, EXTCON_USB) == 0) {
        DBG("receive type-c notifier event: DISCNT...\n");

        rk817_charge_set_chrg_param(charge, USB_TYPE_NONE_CHARGER);
    }
}

static void rk817_charge_bc_evt_worker(struct work_struct *work)
{
    struct rk817_charger *charge = container_of(work,
                            struct rk817_charger,
                            usb_work.work);
    static const char * const event_name[] = {"DISCNT", "USB", "AC",
                          "CDP1.5A", "UNKNOWN",
                          "OTG ON", "OTG OFF"};

    switch (charge->bc_event) {
    case USB_BC_TYPE_DISCNT:
        rk817_charge_set_chrg_param(charge, USB_TYPE_NONE_CHARGER);
        break;
    case USB_BC_TYPE_SDP:
        rk817_charge_set_chrg_param(charge, USB_TYPE_USB_CHARGER);
        break;
    case USB_BC_TYPE_DCP:
        rk817_charge_set_chrg_param(charge, USB_TYPE_AC_CHARGER);
        break;
    case USB_BC_TYPE_CDP:
        rk817_charge_set_chrg_param(charge, USB_TYPE_CDP_CHARGER);
        break;
    case USB_OTG_POWER_ON:
        if (charge->pdata->power_dc2otg && charge->dc_in)
            DBG("otg power from dc adapter\n");
        else
            rk817_charge_set_otg_state(charge, USB_OTG_POWER_ON);
        break;
    case USB_OTG_POWER_OFF:
        rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF);
        break;
    default:
        break;
    }

    DBG("receive bc notifier event: %s..\n", event_name[charge->bc_event]);
}

static int rk817_charger_evt_notifier(struct notifier_block *nb,
                      unsigned long event, void *ptr)
{
    struct rk817_charger *charge =
        container_of(nb, struct rk817_charger, cable_cg_nb);

    queue_delayed_work(charge->usb_charger_wq, &charge->usb_work,
               msecs_to_jiffies(10));

    return NOTIFY_DONE;
}

static int rk817_charge_host_evt_notifier(struct notifier_block *nb,
                      unsigned long event, void *ptr)
{
    struct rk817_charger *charge =
        container_of(nb, struct rk817_charger, cable_host_nb);

    queue_delayed_work(charge->usb_charger_wq, &charge->host_work,
               msecs_to_jiffies(10));

    return NOTIFY_DONE;
}

static int rk817_charge_discnt_evt_notfier(struct notifier_block *nb,
                       unsigned long event, void *ptr)
{
    struct rk817_charger *charge =
        container_of(nb, struct rk817_charger, cable_discnt_nb);

    queue_delayed_work(charge->usb_charger_wq, &charge->discnt_work,
               msecs_to_jiffies(10));

    return NOTIFY_DONE;
}

static int rk817_charge_bc_evt_notifier(struct notifier_block *nb,
                    unsigned long event, void *ptr)
{
    struct rk817_charger *charge =
        container_of(nb, struct rk817_charger, bc_nb);

    charge->bc_event = event;
    queue_delayed_work(charge->usb_charger_wq, &charge->usb_work,
               msecs_to_jiffies(10));

    return NOTIFY_DONE;
}

static int rk817_charge_usb_init(struct rk817_charger *charge)
{
    enum charger_t charger;
    enum bc_port_type bc_type;
    struct extcon_dev *edev;
    struct device *dev = charge->dev;
    int ret;

    charge->usb_charger_wq = alloc_ordered_workqueue("%s",
                WQ_MEM_RECLAIM | WQ_FREEZABLE,
                "rk817-usb-wq");

    /* type-C */
    if (charge->pdata->extcon) {
        edev = extcon_get_edev_by_phandle(dev, 0);
        if (IS_ERR(edev)) {
            if (PTR_ERR(edev) != -EPROBE_DEFER)
                dev_err(dev, "Invalid or missing extcon\n");
            return PTR_ERR(edev);
        }

        /* Register chargers  */
        INIT_DELAYED_WORK(&charge->usb_work, rk817_charger_evt_worker);
        charge->cable_cg_nb.notifier_call = rk817_charger_evt_notifier;
        ret = extcon_register_notifier(edev, EXTCON_CHG_USB_SDP,
                           &charge->cable_cg_nb);
        if (ret < 0) {
            dev_err(dev, "failed to register notifier for SDP\n");
            return ret;
        }

        ret = extcon_register_notifier(edev, EXTCON_CHG_USB_DCP,
                           &charge->cable_cg_nb);
        if (ret < 0) {
            dev_err(dev, "failed to register notifier for DCP\n");
            extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
                           &charge->cable_cg_nb);
            return ret;
        }

        ret = extcon_register_notifier(edev, EXTCON_CHG_USB_CDP,
                           &charge->cable_cg_nb);
        if (ret < 0) {
            dev_err(dev, "failed to register notifier for CDP\n");
            extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
                           &charge->cable_cg_nb);
            extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP,
                           &charge->cable_cg_nb);
            return ret;
        }

        /* Register host */
        INIT_DELAYED_WORK(&charge->host_work,
                  rk817_charge_host_evt_worker);
        charge->cable_host_nb.notifier_call =
            rk817_charge_host_evt_notifier;
        ret = extcon_register_notifier(edev, EXTCON_USB_VBUS_EN,
                           &charge->cable_host_nb);
        if (ret < 0) {
            dev_err(dev, "failed to register notifier for HOST\n");
            extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
                           &charge->cable_cg_nb);
            extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP,
                           &charge->cable_cg_nb);
            extcon_unregister_notifier(edev, EXTCON_CHG_USB_CDP,
                           &charge->cable_cg_nb);

            return ret;
        }

        /* Register discnt usb */
        INIT_DELAYED_WORK(&charge->discnt_work,
                  rk817_charge_discnt_evt_worker);
        charge->cable_discnt_nb.notifier_call =
            rk817_charge_discnt_evt_notfier;
        ret = extcon_register_notifier(edev, EXTCON_USB,
                           &charge->cable_discnt_nb);
        if (ret < 0) {
            dev_err(dev, "failed to register notifier for HOST\n");
            extcon_unregister_notifier(edev, EXTCON_CHG_USB_SDP,
                           &charge->cable_cg_nb);
            extcon_unregister_notifier(edev, EXTCON_CHG_USB_DCP,
                           &charge->cable_cg_nb);
            extcon_unregister_notifier(edev, EXTCON_CHG_USB_CDP,
                           &charge->cable_cg_nb);
            extcon_unregister_notifier(edev, EXTCON_USB_VBUS_EN,
                           &charge->cable_host_nb);
            return ret;
        }

        charge->cable_edev = edev;

        DBG("register typec extcon evt notifier\n");
    } else {
        INIT_DELAYED_WORK(&charge->usb_work,
                  rk817_charge_bc_evt_worker);
        charge->bc_nb.notifier_call = rk817_charge_bc_evt_notifier;
        ret = rk_bc_detect_notifier_register(&charge->bc_nb, &bc_type);
        if (ret) {
            dev_err(dev, "failed to register notifier for bc\n");
            return -EINVAL;
    }

        switch (bc_type) {
        case USB_BC_TYPE_DISCNT:
            charger = USB_TYPE_NONE_CHARGER;
            break;
        case USB_BC_TYPE_SDP:
        case USB_BC_TYPE_CDP:
            charger = USB_TYPE_USB_CHARGER;
            break;
        case USB_BC_TYPE_DCP:
            charger = USB_TYPE_AC_CHARGER;
            break;
        default:
            charger = USB_TYPE_NONE_CHARGER;
            break;
        }

        charge->usb_charger = charger;
        if (charge->dc_charger != DC_TYPE_NONE_CHARGER)
            rk817_charge_set_chrg_param(charge,
                            charge->usb_charger);

        DBG("register bc evt notifier\n");
    }

    return 0;
}

static void rk817_charge_pre_init(struct rk817_charger *charge)
{
    charge->max_chrg_current = charge->pdata->max_chrg_current;
    charge->max_input_current = charge->pdata->max_input_current;
    charge->max_chrg_voltage = charge->pdata->max_chrg_voltage;
    charge->min_input_voltage = charge->pdata->min_input_voltage;
    charge->chrg_finish_cur = charge->pdata->chrg_finish_cur;
    charge->chrg_term_mode = charge->pdata->chrg_term_mode;

    rk817_charge_set_input_voltage(charge, charge->min_input_voltage);

    rk817_charge_set_chrg_voltage(charge, charge->max_chrg_voltage);
    rk817_charge_set_chrg_current(charge, charge->max_chrg_current);

    rk817_charge_set_chrg_finish_condition(charge);

    if (rk817_charge_get_otg_state(charge))
        rk817_charge_otg_disable(charge);
    rk817_charge_field_write(charge, OTG_EN, OTG_DISABLE);
    rk817_charge_set_otg_in(charge, OFFLINE);

    if (!charge->pdata->gate_function_disable)
        rk817_charge_sys_can_sd_disable(charge);
    rk817_charge_usb_to_sys_enable(charge);
    rk817_charge_enable_charge(charge);

    rk817_charge_set_charge_clock(charge, CHRG_CLK_2M);
}

static void rk817_chage_debug(struct rk817_charger *charge)
{
    rk817_charge_get_charge_status(charge);
    DBG("OTG state : %d\n", rk817_charge_get_otg_state(charge));
    DBG("charge state: %d\n", rk817_charge_get_charge_state(charge));
    DBG("max_chrg_current: %d\n"
        "max_input_current: %d\n"
        "min_input_voltage: %d\n"
        "max_chrg_voltage: %d\n"
        "max_chrg_finish_cur: %d\n"
        "chrg_term_mode: %d\n",
        charge->max_chrg_current,
        charge->max_input_current,
        charge->min_input_voltage,
        charge->max_chrg_voltage,
        charge->chrg_finish_cur,
        charge->chrg_term_mode);
}

static int rk817_charge_get_otg5v_regulator(struct rk817_charger *charge)
{
    int ret;

    charge->otg5v_rdev = devm_regulator_get(charge->dev, "otg_switch");
    if (IS_ERR(charge->otg5v_rdev)) {
        ret = PTR_ERR(charge->otg5v_rdev);
        dev_warn(charge->dev, "failed to get otg regulator: %d\n", ret);
    }

    return 0;
}

#ifdef CONFIG_OF
static int rk817_charge_parse_dt(struct rk817_charger *charge)
{
    struct charger_platform_data *pdata;
    enum of_gpio_flags flags;
    struct device *dev = charge->dev;
    struct device_node *np = charge->dev->of_node;
    int ret;

    pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
    if (!pdata)
        return -ENOMEM;

    charge->pdata = pdata;
    pdata->max_chrg_current = DEFAULT_CHRG_CURRENT;
    pdata->max_input_current = DEFAULT_INPUT_CURRENT;
    pdata->max_chrg_voltage = DEFAULT_CHRG_VOLTAGE;
    pdata->min_input_voltage = DEFAULT_INPUT_VOLTAGE;
    pdata->chrg_finish_cur = DEFAULT_CHRG_TERM_CUR;
    pdata->chrg_term_mode = DEFAULT_CHRG_TERM_MODE;

    pdata->extcon = of_property_read_bool(np, "extcon");

    ret = of_property_read_u32(np, "max_chrg_current",
                   &pdata->max_chrg_current);
    if (ret < 0)
        dev_err(dev, "max_chrg_current missing!\n");

    ret = of_property_read_u32(np, "max_input_current",
                   &pdata->max_input_current);
    if (ret < 0)
        dev_err(dev, "max_input_current missing!\n");

    ret = of_property_read_u32(np, "max_chrg_voltage",
                   &pdata->max_chrg_voltage);
    if (ret < 0)
        dev_err(dev, "max_chrg_voltage missing!\n");

    ret = of_property_read_u32(np, "min_input_voltage",
                   &pdata->min_input_voltage);
    if (ret < 0)
        dev_WARN(dev, "min_input_voltage missing!\n");

    ret = of_property_read_u32(np, "chrg_finish_cur",
                   &pdata->chrg_finish_cur);

    if (ret < 0)
        dev_WARN(dev, "chrg_term_mode missing!\n");

    ret = of_property_read_u32(np, "chrg_term_mode",
                   &pdata->chrg_term_mode);
    if (ret < 0)
        dev_WARN(dev, "chrg_term_mode missing!\n");

    ret = of_property_read_u32(np, "virtual_power", &pdata->virtual_power);
    if (ret < 0)
        dev_err(dev, "virtual_power missing!\n");

    ret = of_property_read_u32(np, "power_dc2otg", &pdata->power_dc2otg);
    if (ret < 0)
        dev_err(dev, "power_dc2otg missing!\n");

    ret = of_property_read_u32(np, "sample_res", &pdata->sample_res);
    if (ret < 0) {
        pdata->sample_res = SAMPLE_RES_10MR;
        dev_err(dev, "sample_res missing!\n");
    }

    ret = of_property_read_u32(np, "otg5v_suspend_enable",
                   &pdata->otg5v_suspend_enable);
    if (ret < 0) {
        pdata->otg5v_suspend_enable = 1;
        dev_err(dev, "otg5v_suspend_enable missing!\n");
    }

    ret = of_property_read_u32(np, "gate_function_disable",
                   &pdata->gate_function_disable);
    if (ret < 0)
        dev_err(dev, "gate_function_disable missing!\n");

    if (!is_battery_exist(charge))
        pdata->virtual_power = 1;

    charge->res_div = (charge->pdata->sample_res == SAMPLE_RES_10MR) ?
               SAMPLE_RES_DIV1 : SAMPLE_RES_DIV2;

    if (!of_find_property(np, "dc_det_gpio", &ret)) {
        pdata->support_dc_det = false;
        DBG("not support dc\n");
    } else {
        pdata->support_dc_det = true;
        pdata->dc_det_pin = of_get_named_gpio_flags(np, "dc_det_gpio",
                                0, &flags);
        if (gpio_is_valid(pdata->dc_det_pin)) {
            DBG("support dc\n");
            pdata->dc_det_level = (flags & OF_GPIO_ACTIVE_LOW) ?
                           0 : 1;
        } else {
            dev_err(dev, "invalid dc det gpio!\n");
            return -EINVAL;
        }
    }

    DBG("input_current:%d\n"
        "input_min_voltage: %d\n"
        "chrg_current:%d\n"
        "chrg_voltage:%d\n"
        "sample_res:%d\n"
        "extcon:%d\n"
        "virtual_power:%d\n"
        "power_dc2otg:%d\n",
        pdata->max_input_current, pdata->min_input_voltage,
        pdata->max_chrg_current,  pdata->max_chrg_voltage,
        pdata->sample_res, pdata->extcon,
        pdata->virtual_power, pdata->power_dc2otg);

    return 0;
}
#else
static int rk817_charge_parse_dt(struct rk817_charger *charge)
{
    return -ENODEV;
}
#endif

static void rk817_charge_irq_delay_work(struct work_struct *work)
{
    struct rk817_charger *charge = container_of(work,
            struct rk817_charger, irq_work.work);

    if (charge->plugin_trigger) {
        DBG("pmic: plug in\n");
        charge->plugin_trigger = 0;
        if (charge->pdata->extcon)
            queue_delayed_work(charge->usb_charger_wq, &charge->usb_work,
                       msecs_to_jiffies(10));
    } else if (charge->plugout_trigger) {
        DBG("pmic: plug out\n");
        charge->plugout_trigger = 0;
        rk817_charge_set_chrg_param(charge, USB_TYPE_NONE_CHARGER);
        rk817_charge_set_chrg_param(charge, DC_TYPE_NONE_CHARGER);
    } else {
        DBG("pmic: unknown irq\n");
    }
}

static irqreturn_t rk817_plug_in_isr(int irq, void *cg)
{
    struct rk817_charger *charge;

    charge = (struct rk817_charger *)cg;
    charge->plugin_trigger = 1;
    queue_delayed_work(charge->usb_charger_wq, &charge->irq_work,
               msecs_to_jiffies(10));

    return IRQ_HANDLED;
}

static irqreturn_t rk817_plug_out_isr(int irq, void *cg)
{
    struct rk817_charger *charge;

    charge = (struct rk817_charger *)cg;
    charge->plugout_trigger = 1;
    queue_delayed_work(charge->usb_charger_wq, &charge->irq_work,
               msecs_to_jiffies(10));

    return IRQ_HANDLED;
}

static int rk817_charge_init_irqs(struct rk817_charger *charge)
{
    struct rk808 *rk817 = charge->rk817;
    struct platform_device *pdev = charge->pdev;
    int ret, plug_in_irq, plug_out_irq;

    plug_in_irq = regmap_irq_get_virq(rk817->irq_data, RK817_IRQ_PLUG_IN);
    if (plug_in_irq < 0) {
        dev_err(charge->dev, "plug_in_irq request failed!\n");
        return plug_in_irq;
    }

    plug_out_irq = regmap_irq_get_virq(rk817->irq_data, RK817_IRQ_PLUG_OUT);
    if (plug_out_irq < 0) {
        dev_err(charge->dev, "plug_out_irq request failed!\n");
        return plug_out_irq;
    }

    ret = devm_request_threaded_irq(charge->dev, plug_in_irq, NULL,
                    rk817_plug_in_isr,
                    IRQF_TRIGGER_RISING | IRQF_ONESHOT,
                    "rk817_plug_in", charge);
    if (ret) {
        dev_err(&pdev->dev, "plug_in_irq request failed!\n");
        return ret;
    }

    ret = devm_request_threaded_irq(charge->dev, plug_out_irq, NULL,
                    rk817_plug_out_isr,
                    IRQF_TRIGGER_RISING | IRQF_ONESHOT,
                    "rk817_plug_out", charge);
    if (ret) {
        dev_err(&pdev->dev, "plug_out_irq request failed!\n");
        return ret;
    }

    charge->plugin_irq = plug_in_irq;
    charge->plugout_irq = plug_out_irq;

    INIT_DELAYED_WORK(&charge->irq_work, rk817_charge_irq_delay_work);

    return 0;
}

static const struct of_device_id rk817_charge_of_match[] = {
    { .compatible = "rk817,charger", },
    { },
};

static int rk817_charge_probe(struct platform_device *pdev)
{
    struct rk808 *rk817 = dev_get_drvdata(pdev->dev.parent);
    const struct of_device_id *of_id =
            of_match_device(rk817_charge_of_match, &pdev->dev);
    struct i2c_client *client = rk817->i2c;
    struct rk817_charger *charge;
    int i;
    int ret;

    if (!of_id) {
        dev_err(&pdev->dev, "Failed to find matching dt id\n");
        return -ENODEV;
    }

    charge = devm_kzalloc(&pdev->dev, sizeof(*charge), GFP_KERNEL);
    if (!charge)
        return -EINVAL;

    charge->rk817 = rk817;
    charge->pdev = pdev;
    charge->dev = &pdev->dev;
    charge->client = client;
    platform_set_drvdata(pdev, charge);

    charge->regmap = rk817->regmap;
    if (IS_ERR(charge->regmap)) {
        dev_err(charge->dev, "Failed to initialize regmap\n");
        return -EINVAL;
    }

    for (i = 0; i < ARRAY_SIZE(rk817_charge_reg_fields); i++) {
        const struct reg_field *reg_fields = rk817_charge_reg_fields;

        charge->rmap_fields[i] =
            devm_regmap_field_alloc(charge->dev,
                        charge->regmap,
                        reg_fields[i]);
        if (IS_ERR(charge->rmap_fields[i])) {
            dev_err(charge->dev, "cannot allocate regmap field\n");
            return PTR_ERR(charge->rmap_fields[i]);
        }
    }

    ret = rk817_charge_parse_dt(charge);
    if (ret < 0) {
        dev_err(charge->dev, "charge parse dt failed!\n");
        return ret;
    }
    rk817_charge_get_otg5v_regulator(charge);

    rk817_charge_pre_init(charge);

    ret = rk817_charge_init_power_supply(charge);
    if (ret) {
        dev_err(charge->dev, "init power supply fail!\n");
        return ret;
    }

    ret = rk817_charge_init_dc(charge);
    if (ret) {
        dev_err(charge->dev, "init dc failed!\n");
        return ret;
    }

    ret = rk817_charge_usb_init(charge);
    if (ret) {
        dev_err(charge->dev, "init usb failed!\n");
        return ret;
    }

    ret = rk817_charge_init_irqs(charge);
    if (ret) {
        dev_err(charge->dev, "init irqs failed!\n");
        goto irq_fail;
    }

    if (charge->pdata->extcon) {
        schedule_delayed_work(&charge->host_work, 0);
        schedule_delayed_work(&charge->usb_work, 0);
    }

    rk817_chage_debug(charge);
    DBG("driver version: %s\n", CHARGE_DRIVER_VERSION);

    return 0;
irq_fail:
    if (charge->pdata->extcon) {
        cancel_delayed_work_sync(&charge->host_work);
        cancel_delayed_work_sync(&charge->discnt_work);
    }

    cancel_delayed_work_sync(&charge->usb_work);
    cancel_delayed_work_sync(&charge->dc_work);
    cancel_delayed_work_sync(&charge->irq_work);
    destroy_workqueue(charge->usb_charger_wq);
    destroy_workqueue(charge->dc_charger_wq);

    if (charge->pdata->extcon) {
        extcon_unregister_notifier(charge->cable_edev,
                       EXTCON_CHG_USB_SDP,
                       &charge->cable_cg_nb);
        extcon_unregister_notifier(charge->cable_edev,
                       EXTCON_CHG_USB_DCP,
                       &charge->cable_cg_nb);
        extcon_unregister_notifier(charge->cable_edev,
                       EXTCON_CHG_USB_CDP,
                       &charge->cable_cg_nb);
        extcon_unregister_notifier(charge->cable_edev,
                       EXTCON_USB_VBUS_EN,
                       &charge->cable_host_nb);
        extcon_unregister_notifier(charge->cable_edev,
                       EXTCON_USB,
                       &charge->cable_discnt_nb);
    } else {
        rk_bc_detect_notifier_unregister(&charge->bc_nb);
    }

    return ret;
}

#ifdef CONFIG_PM_SLEEP
static int  rk817_charge_pm_suspend(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct rk817_charger *charge = dev_get_drvdata(&pdev->dev);

    charge->otg_slp_state = rk817_charge_get_otg_slp_state(charge);

    /* enable sleep boost5v and otg5v */
    if (charge->pdata->otg5v_suspend_enable) {
        if ((charge->otg_in && !charge->dc_in) ||
            (charge->otg_in && charge->dc_in &&
            !charge->pdata->power_dc2otg)) {
            rk817_charge_otg_slp_enable(charge);
            DBG("suspend: otg 5v on\n");
            return 0;
        }
    }

    /* disable sleep otg5v */
    rk817_charge_otg_slp_disable(charge);
    DBG("suspend: otg 5v off\n");
    return 0;
}

static int rk817_charge_pm_resume(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct rk817_charger *charge = dev_get_drvdata(&pdev->dev);

    /* resume sleep boost5v and otg5v */
    if (charge->otg_slp_state)
        rk817_charge_otg_slp_enable(charge);

    return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(rk817_charge_pm_ops,
    rk817_charge_pm_suspend, rk817_charge_pm_resume);

static void rk817_charger_shutdown(struct platform_device *dev)
{
    struct rk817_charger *charge =  platform_get_drvdata(dev);

    /* type-c only */
    if (charge->pdata->extcon) {
        cancel_delayed_work_sync(&charge->host_work);
        cancel_delayed_work_sync(&charge->discnt_work);
    }

    rk817_charge_set_otg_state(charge, USB_OTG_POWER_OFF);
    rk817_charge_boost_disable(charge);
    disable_irq(charge->plugin_irq);
    disable_irq(charge->plugout_irq);

    cancel_delayed_work_sync(&charge->usb_work);
    cancel_delayed_work_sync(&charge->dc_work);
    cancel_delayed_work_sync(&charge->irq_work);
    flush_workqueue(charge->usb_charger_wq);
    flush_workqueue(charge->dc_charger_wq);

    if (charge->pdata->extcon) {
        extcon_unregister_notifier(charge->cable_edev,
                       EXTCON_CHG_USB_SDP,
                       &charge->cable_cg_nb);
        extcon_unregister_notifier(charge->cable_edev,
                       EXTCON_CHG_USB_DCP,
                       &charge->cable_cg_nb);
        extcon_unregister_notifier(charge->cable_edev,
                       EXTCON_CHG_USB_CDP,
                       &charge->cable_cg_nb);
        extcon_unregister_notifier(charge->cable_edev,
                       EXTCON_USB_VBUS_EN,
                       &charge->cable_host_nb);
        extcon_unregister_notifier(charge->cable_edev, EXTCON_USB,
                       &charge->cable_discnt_nb);
    } else {
        rk_bc_detect_notifier_unregister(&charge->bc_nb);
    }

    DBG("shutdown: ac=%d usb=%d dc=%d otg=%d\n",
        charge->ac_in, charge->usb_in, charge->dc_in, charge->otg_in);
}

static struct platform_driver rk817_charge_driver = {
    .probe = rk817_charge_probe,
    .shutdown = rk817_charger_shutdown,
    .driver = {
        .name = "rk817-charger",
        .pm = &rk817_charge_pm_ops,
        .of_match_table = of_match_ptr(rk817_charge_of_match),
    },
};

static int __init rk817_charge_init(void)
{
    return platform_driver_register(&rk817_charge_driver);
}
fs_initcall_sync(rk817_charge_init);

static void __exit rk817_charge_exit(void)
{
    platform_driver_unregister(&rk817_charge_driver);
}
module_exit(rk817_charge_exit);

MODULE_DESCRIPTION("RK817 Charge driver");
MODULE_LICENSE("GPL");
